package ru.papergames.web.blines.battle;


import javolution.util.FastMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import ru.common.network.clientcore.ClientSelectorThread;
import ru.common.network.clientcore.OnFinishConnectionListener;
import ru.papergames.common.beans.BattleMember;
import ru.papergames.common.form.BattleServerCredentialsForm;
import ru.papergames.common.form.NpcPlayerForm;
import ru.papergames.common.form.PcPlayerForm;
import ru.papergames.web.blines.battle.model.BattleOrder2;
import ru.papergames.web.blines.battle.network.ClientWebServer;
import ru.papergames.web.blines.battle.network.clientpackets.*;

import java.io.IOException;
import java.net.InetAddress;
import java.util.List;

/**
 * @author: atroshanin
 * Date: 29.05.2010
 * Time: 21:54:33
 */
public class BattleServerConnectionsHolderThread extends Thread {

    private static Log log = LogFactory.getLog(BattleServerConnectionsHolderThread.class);

    private static final long MAX_CONNECTION_PERIOD = 20 * 60 * 1000;

    private static final Object LOCK = new Object();

    private static final int ITEMS_CHUNK_SIZE = 20;

    private BattleServerConnectionHolder[] holders;
    private volatile boolean shutdown;

    public BattleServerConnectionsHolderThread(ClientSelectorThread selectorThread, List<BattleServerCredentialsForm> servers) {
        holders = new BattleServerConnectionHolder[servers.size()];
        int i = 0;
        for (BattleServerCredentialsForm serverCredentials : servers) {
            holders[i] = new BattleServerConnectionHolder(1000 + i, serverCredentials, selectorThread);
            i++;
        }
    }

    public void run() {

        while (!shutdown) {
            synchronized (LOCK) {
                if (shutdown) {
                    break;
                }
                boolean allConnected = true;
                for (BattleServerConnectionHolder holder : holders) {
                    ClientWebServer client = holder.getClientWebServer();
                    if (client == null) {
                        allConnected = false;
                        try {
                            holder.tryToConnect();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    } else if (holder.isNeedToReconnect()) {
                        allConnected = false;
                        try {
                            holder.tryToConnect();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
                try {
                    if (allConnected) {
                        LOCK.wait();
                    } else {
                        LOCK.wait(15 * 1000);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace(); // THIS might never occur...
                    shutdown = true;
                }
            }

        }

    }

    public void shutdown() {
        synchronized (LOCK) {
            shutdown = true;
            LOCK.notify();
        }
        for (BattleServerConnectionHolder holder : holders) {
            ClientWebServer client = holder.getClientWebServer();
            if (client != null) {
                client.closeSocket();
            }
        }
    }


    public BattleServerConnectionHolder getBattleServerConnectionHolder() {
        BattleServerConnectionHolder result = holders[0];
        log.info("selectingBattleServerConnectionHolder.\nholders size: " + holders.length + ", preselected holder[" + holders[0] + "]");
        for (BattleServerConnectionHolder holder : holders) {
            log.info("current holder: " + holder);
            ClientWebServer client = holder.getClientWebServer();
            if (client != null) {
                if (holder.currentBattlesCount < result.currentBattlesCount) {
                    result = holder;
                }
            }
        }
        log.info("selected holder: " + result);
        return result;
    }

    public int getOnlineUsersCount() {
        if (holders == null) {
            log.error("[ALARM] No messaging holders found(holders IS NULL)");
            return 0;
        }
        int res = 0;
        for (BattleServerConnectionHolder holder : holders) {
            res += (holder != null ? holder.currentUsersCount : 0);
        }
        return res;
    }

    public class BattleServerConnectionHolder {

        private int webServerId;

        private BattleServerCredentialsForm serverCredentials;

        private ClientWebServer clientWebServer;
        private ClientWebServer oldClientWebServer;
        private ClientSelectorThread selectorThread;

        private long connectTime;

        private int currentUsersCount;

        private int currentBattlesCount;
        private int requestIdSequence = 1;

        private boolean sendItems;

        private FastMap<Integer, BattleLifeCycleListener> battleLifeCycleListenersForCreate = new FastMap<Integer, BattleLifeCycleListener>().shared();
        private FastMap<Integer, BattleLifeCycleListener> battleLifeCycleListenersForFight = new FastMap<Integer, BattleLifeCycleListener>().shared();

        BattleServerConnectionHolder(int webServerId, BattleServerCredentialsForm serverCredentials, ClientSelectorThread selectorThread) {
            this.webServerId = webServerId;
            this.serverCredentials = serverCredentials;
            this.selectorThread = selectorThread;
        }

        public int getWebServerId() {
            return webServerId;
        }

        public boolean isSendItems() {
            return sendItems;
        }

        public void setSendItems(boolean sendItems) {
            this.sendItems = sendItems;
        }

        public ClientWebServer getClientWebServer() {
            return clientWebServer;
        }

        public void setClientWebServer(ClientWebServer clientWebServer) {
            System.out.println("Saving old connection if it is...");
            this.oldClientWebServer = this.clientWebServer;
            this.clientWebServer = clientWebServer;
            this.clientWebServer.setConnectionHolder(this);
            connectTime = System.currentTimeMillis();
        }

        public void disconnectOldClientWebServer() {
            if (oldClientWebServer != null) {
                System.out.println("=============================================================");
                System.out.println("=================CLOSING PREVIOUS CONNECTION=================");
                System.out.println("=============================================================");
                oldClientWebServer.setConnectionHolder(null);
                oldClientWebServer.getConnection().close(new PlannedDisconnection());
            }
        }

        public void tryToConnect() throws IOException {
            InetAddress inetAdr = serverCredentials.getHost().equals("localhost") ? InetAddress.getLocalHost() : InetAddress.getByName(serverCredentials.getHost());
            connectTime = -1;
            selectorThread.openConnectionToServer(inetAdr, serverCredentials.getPort(), new OnFinishConnectionListenerImpl(this));
        }

        public boolean isNeedToReconnect() {
            if (connectTime > 0) {
                long connectPeriod = System.currentTimeMillis() - connectTime;
                if (connectPeriod > MAX_CONNECTION_PERIOD) {
                    System.out.println("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
                    System.out.println("+++++++++++++++++++++++++++++++++++NEED TO RECONNECT+++++++++++++++++++++++++++++++++++");
                    System.out.println("++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
                    return true;
                }
            }
            return false;
        }

        public long getConnectTime() {
            return connectTime;
        }

        public void onConnectionClose() {
            clientWebServer = null;
            sendItems = true;
            synchronized (LOCK) {
                LOCK.notify();
            }
        }

        public boolean addNpcPlayersToBattleServer(List<NpcPlayerForm> npcPlayers) {
            ClientWebServer client = clientWebServer;
            if (client != null && client.getState() == ClientWebServer.ClientWebServerState.AUTHED) {
                log.info("sending packet AddNewNpcPlayers!!!");
                client.sendPacket(new AddNewNpcPlayers(npcPlayers));
                return true;
            }
            return false;
        }

        public boolean addNewPlayerToBattleServer(PcPlayerForm player, int secretKey) {
            ClientWebServer client = clientWebServer;
            if (client != null && client.getState() == ClientWebServer.ClientWebServerState.AUTHED) {
                log.info("sending packet AddNewPlayer!!!");
                client.sendPacket(new AddNewPcPlayer(player, secretKey));
                return true;
            }
            return false;
        }

        public boolean createBattle(BattleLifeCycleListener listener, BattleOrder2 battleOrder) {
            ClientWebServer client = clientWebServer;
            if (client != null && client.getState() == ClientWebServer.ClientWebServerState.AUTHED) {
                currentBattlesCount++;
                battleLifeCycleListenersForCreate.put(battleOrder.getId(), listener);
                client.sendPacket(new CreateNewBattle(battleOrder));
                return true;
            }
            return false;
        }

/*
        public boolean createTutorialBattleWithNpc(BattleLifeCycleListener listener, BattleOrder2 battleOrder, PcPlayerForm pcPlayer, List<NpcPlayerForm> npcPlayers) {
            ClientWebServer client = clientWebServer;
            if (client != null && client.getState() == ClientWebServer.ClientWebServerState.AUTHED) {
                currentBattlesCount++;
                int requestId = generateRequestId();
                battleLifeCycleListenersForCreate.put(requestId, listener);
                client.sendPacket(new CreateNewTutorialBattleWithNpc(requestId, battleOrder, pcPlayer, npcPlayers));
                return true;
            }
            return false;
        }

        public boolean joinExistingBattle(BattleOrder2 battleOrder, PcPlayerForm pcPlayer) {
            ClientWebServer client = clientWebServer;
            if (client != null && client.getState() == ClientWebServer.ClientWebServerState.AUTHED) {
                client.sendPacket(new JoinOsidePcPlayerToExistingBattleRequest(battleOrder, pcPlayer));
                return true;
            }
            return false;
        }

        public boolean viewExistingBattle(BattleOrder2 battleOrder, PcPlayerForm pcPlayer) {
            ClientWebServer client = clientWebServer;
            if (client != null && client.getState() == ClientWebServer.ClientWebServerState.AUTHED) {
                client.sendPacket(new JoinOsidePcPlayerAsViewerToExistingBattleRequest(battleOrder, pcPlayer));
                return true;
            }
            return false;
        }
*/

        /*
        public boolean sendItems(FastMap<Integer, Item> allItems) {
            ClientWebServer client = clientWebServer;
            if (client != null && client.getState() == ClientWebServer.ClientWebServerState.AUTHED) {
                List<List<Item>> chunks = new LinkedList<List<Item>>();
                List<Item> chunk = new LinkedList<Item>();
                for (FastMap.Entry<Integer, Item> e = allItems.head(), end = allItems.tail(); (e = e.getNext()) != end;) {
                    chunk.add(e.getValue());
                    if (chunk.size() >= ITEMS_CHUNK_SIZE) {
                        chunks.add(chunk);
                        chunk = new LinkedList<Item>();
                    }
                }
                if (chunk.size() > 0) {
                    chunks.add(chunk);
                }
                for (List<Item> items : chunks) {
                    client.sendPacket(new AddOsideItems(items));
                }
                return true;
            }
            return false;
        }
        */

        private synchronized int generateRequestId() {
            return requestIdSequence++;
        }

        public void onBattleCreateOk(int battleOrderId) {
            BattleLifeCycleListener listener = battleLifeCycleListenersForCreate.remove(battleOrderId);
            if (listener != null) {
                listener.onBattleCreateOk(battleOrderId, serverCredentials);
                battleLifeCycleListenersForFight.put(battleOrderId, listener);
            }
        }

        public void onBattleCreateFail(int battleOrderId) {
            BattleLifeCycleListener listener = battleLifeCycleListenersForCreate.remove(battleOrderId);
            if (listener != null) {
                currentBattlesCount--;
                listener.onBattleCreateFail();
            }
        }

        public void onBattleFinish(int battleOrderId, int winnerTeamId, List<BattleMember> pcPlayers) {
            BattleLifeCycleListener listener = battleLifeCycleListenersForFight.remove(battleOrderId);
            if (listener != null) {
                currentBattlesCount--;
                listener.onBattleFinish(winnerTeamId, pcPlayers);
            }
        }

        public void onBattleStart(int battleOrderId) {
            BattleLifeCycleListener listener = battleLifeCycleListenersForFight.get(battleOrderId);
            if (listener != null) {
                listener.onBattleStart();
            }
        }

        public BattleServerCredentialsForm getServerCredentials() {
            return serverCredentials;
        }

        public void incUsersCount() {
            currentUsersCount++;
        }

        public void decUsersCount() {
            currentUsersCount--;
            currentUsersCount = (currentUsersCount < 0) ? 0 : currentUsersCount;
        }

        public void onForceReconnectRequest() {
            connectTime = 1000;
            synchronized (LOCK) {
                LOCK.notify();
            }
        }

        public String toString() {
            return "BattleServerConnectionHolder:\nwebServerId[" + webServerId + "],\nclientWebServer[" + clientWebServer + "],\ncurrentBattlesCount[" + currentBattlesCount + "]";
        }

    }


    private class OnFinishConnectionListenerImpl<T extends ClientWebServer> implements OnFinishConnectionListener<T> {

        private BattleServerConnectionHolder holder;

        OnFinishConnectionListenerImpl(BattleServerConnectionHolder holder) {
            this.holder = holder;
        }

        public void onConnectionOk(T client) {
            client.sendPacket(new AuthorizeWebServer(holder.getWebServerId()));
            client.setConnectionHolder(holder);
        }

        public void onConnectionFail() {

        }
    }

}


